package com.dhcc.bpm.modules.scf.util;

import com.dhcc.bpm.modules.scf.mapper.ScfFileAttachmentInfoMapper;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class OBSUtil {
    @Autowired
    private ObsClient obsClient;
    @Autowired
    private ScfFileAttachmentInfoMapper scfFileAttachmentInfoMapper;
    @Value("${obs.bucketName}")
    private String bucketName;
    @Value("${obs.tempPath}")
    private String tempPath;

    /**
     * 文件上传方法-分段上传大文件
     *
     * @param multipartFile
     * @param objectKey
     * @throws Exception
     */
    public HashMap<String, String> uploadSegmentedFile(MultipartFile multipartFile, String objectKey) throws Exception {
        // 定义返回HashMap集合
        HashMap<String, String> hashMap = new HashMap<>();
        // 检查桶是否存在，不存在则创建桶
        if (!obsClient.headBucket(bucketName)) {
            CreateBucketRequest request = new CreateBucketRequest();
            request.setBucketName(bucketName);
            request.setLocation("cn-north-4");
            obsClient.createBucket(request);
        }
        // 初始化线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        // 初始化分段上传任务
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, tempPath + objectKey);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);

        final String uploadId = result.getUploadId();
        System.out.println("\t" + uploadId + "\n");

        // 每段上传100MB
        long partSize = 5 * 1024 * 1024;
        long fileSize = multipartFile.getSize();

        // 计算需要上传的段数
        long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;

        final List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<PartEtag>());

        // 执行并发上传段
        for (int i = 0; i < partCount; i++) {
            // 分段在文件中的起始位置
            final long offset = i * partSize;
            // 分段大小
            final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
            // 分段号
            final int partNumber = i + 1;
            executorService.execute(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    UploadPartRequest uploadPartRequest = new UploadPartRequest();
                    uploadPartRequest.setBucketName(bucketName);
                    uploadPartRequest.setObjectKey(tempPath + objectKey);
                    uploadPartRequest.setUploadId(uploadId);
                    uploadPartRequest.setInput(multipartFile.getInputStream());
                    uploadPartRequest.setPartSize(currPartSize);
                    uploadPartRequest.setOffset(offset);
                    uploadPartRequest.setPartNumber(partNumber);
                    UploadPartResult uploadPartResult;
                    try {
                        uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                        System.out.println("Part#" + partNumber + " done\n");
                        partEtags.add(new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
                    } catch (ObsException e) {
                        e.printStackTrace();
                        // 取消分段上传任务
                        obsClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, tempPath + objectKey, uploadId));
                    }
                }
            });
        }

        // 等待上传完成
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                executorService.awaitTermination(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 合并段
        CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, tempPath + objectKey, uploadId, partEtags);
        try {
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
        } catch (Exception e) {
            // 取消分段上传任务
            obsClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, tempPath + objectKey, uploadId));
            log.error("文件上传失败，分段任务已取消：", e);
            throw new Exception("文件上传失败，分段任务已取消！");
        }

        hashMap.put("bucketName", result.getBucketName());
        hashMap.put("objectKey", result.getObjectKey());
        return hashMap;
    }

    /**
     * 上传文件方法-直接上传
     *
     * @param multipartFile
     * @param objectKey
     * @return
     * @throws Exception
     */
    public HashMap<String, String> uploadFile(MultipartFile multipartFile, String objectKey) throws Exception {
        // 定义返回HashMap集合
        HashMap<String, String> hashMap = new HashMap<>();
        // 检查桶是否存在，不存在则创建桶
        if (!obsClient.headBucket(bucketName)) {
            CreateBucketRequest request = new CreateBucketRequest();
            request.setBucketName(bucketName);
            request.setLocation("cn-north-4");
            obsClient.createBucket(request);
        }
        PutObjectResult result = obsClient.putObject(bucketName, tempPath + objectKey, multipartFile.getInputStream());

        hashMap.put("bucketName", result.getBucketName());
        hashMap.put("objectKey", result.getObjectKey());
        return hashMap;
    }


    /**
     * 获取临时访问url
     *
     * @param objectkey 上传路径 (不带桶名的路径)
     * @return
     */
    public String getDownloadUrl(String objectkey) {
        // URL有效期，3600秒
        long expireSeconds = 3600L;
        TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds);
        // 设置桶名,一般都是写在配置里，这里直接赋值即可
        request.setBucketName(bucketName);
        // 这里相当于设置你上传到obs的文件路
        request.setObjectKey(objectkey);
        TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
        return response.getSignedUrl();
    }

    /**
     * 返回字节流形式，实现文件下载+预览方法
     *
     * @param objectKey
     * @return
     * @throws ObsException
     * @throws IOException
     * @throws IOException
     */
    public byte[] downloadFileAsBytes(String objectKey) throws ObsException, IOException {
        ObsObject obsObject = null;
        try {
            obsObject = obsClient.getObject(bucketName, objectKey);
        } catch (ObsException e) {
            throw new ObsException("附件不存在，请更新附件！");
        }

        try (InputStream inputStream = obsObject.getObjectContent()) {
            return IOUtils.toByteArray(inputStream);
        }
    }

    /**
     * @param objectKey 文件删除方法
     * @return
     */
    public Boolean deleteFile(String objectKey) {
        try {
            // 调用 deleteObject 方法删除对象
            obsClient.deleteObject(bucketName, objectKey);
            return true;
        } catch (ObsException e) {
            log.error("文件删除失败:{}", e.getMessage(), e);
        }
        return false;
    }

    /**
     * 移动临时文件，从临时文件夹至正式文件夹
     *
     * @param destObjectKey   新文件
     * @param sourceObjectKey 源文件
     */
    public Boolean copyFile(String destObjectKey, String sourceObjectKey) {
        // 初始化线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);

        // 初始化分段上传任务
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, destObjectKey);
        InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);

        final String uploadId = result.getUploadId();
        System.out.println("\t" + uploadId + "\n");

        try {
            ObjectMetadata metadata = obsClient.getObjectMetadata(bucketName, sourceObjectKey);

            // 获取大对象信息

            // 每段复制100MB
            long partSize = 5 * 1024 * 1024L;
            long objectSize = metadata.getContentLength();

            // 计算需要复制的段数
            long partCount = objectSize % partSize == 0 ? objectSize / partSize : objectSize / partSize + 1;

            final List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<PartEtag>());

            // 执行并发复制段
            for (int i = 0; i < partCount; i++) {
                // 复制段起始位置
                final long rangeStart = i * partSize;
                // 复制段结束位置
                final long rangeEnd = (i + 1 == partCount) ? objectSize - 1 : rangeStart + partSize - 1;
                // 分段号
                final int partNumber = i + 1;
                executorService.execute(new Runnable() {

                    @Override
                    public void run() {
                        CopyPartRequest request = new CopyPartRequest();
                        request.setUploadId(uploadId);
                        request.setSourceBucketName(bucketName);
                        request.setSourceObjectKey(sourceObjectKey);
                        request.setDestinationBucketName(bucketName);
                        request.setDestinationObjectKey(destObjectKey);
                        request.setByteRangeStart(rangeStart);
                        request.setByteRangeEnd(rangeEnd);
                        request.setPartNumber(partNumber);
                        CopyPartResult result;
                        try {
                            result = obsClient.copyPart(request);
                            System.out.println("Part#" + partNumber + " done\n");
                            partEtags.add(new PartEtag(result.getEtag(), result.getPartNumber()));
                        } catch (ObsException e) {
                            // 取消分段上传任务
                            obsClient.abortMultipartUpload(new AbortMultipartUploadRequest(bucketName, destObjectKey, uploadId));
                            e.printStackTrace();
                        }
                    }
                });
            }


            // 等待复制完成
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return false;
                }
            }

            // 合并段
            CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, destObjectKey, uploadId, partEtags);
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
            // 删除原有temp/中文件
            String extractFileName = extractFileName(sourceObjectKey);
            String tempFilePath = tempPath + extractFileName;
            Boolean deleteFile = deleteFile(sourceObjectKey + tempFilePath);
            log.info("移动文件完成！", deleteFile);
        } catch (ObsException obse) {
            obse.printStackTrace();
            return  false;
        }

        return true;
    }


    /**
     * 定时清理任务，每天午夜十二点执行，删除关联id为空的记录
     */
    @Scheduled(cron = "0 0 0 * * ?")
    // @Scheduled(cron = "0/5 * * * * ?")
    public void deleteUnusedFiles() {
        // 查询数据库获取文件信息表中未与任何其他对象关联的临时文件，获取这些objectkeys
        List<String> objectKeys = scfFileAttachmentInfoMapper.getNullFilePath();
        for (String objectKey : objectKeys) {
            // 遍历查询到的objectkeys，循环删除每一个临时文件
            deleteFile(objectKey);
        }
        // 文件删除完成后，同步清理对应的所有未关联的文件记录
        scfFileAttachmentInfoMapper.deleteUnusedFiles();
    }

    /**
     * 文件名获取
     *
     * @param objectKey
     * @return
     */
    public String extractFileName(String objectKey) {
        // 从objectKey中提取文件名
        int lastIndexOfSlash = objectKey.lastIndexOf("/");
        if (lastIndexOfSlash == -1) {
            return objectKey; // 没有斜杠，直接返回
        } else {
            return objectKey.substring(lastIndexOfSlash + 1);
        }
    }
}